package edu.hawaii.myisern.sanitycheck;

import java.io.IOException;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.xml.sax.SAXException;

import com.meterware.httpunit.WebConversation;
import com.meterware.httpunit.WebResponse;

import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.organizations.jaxb.Organization;
import edu.hawaii.myisern.researchers.jaxb.Researcher;
/**
 * Generates a report so as to fix invalid data.
 * @author John Hauage
 *
 */
public class SanityCheck {
	private List<SanityCheckErrorItem> report;
	private ConcurrentMap<String, Collaboration> collaborations;
	private ConcurrentMap<String, Organization> organizations;
	private ConcurrentMap<String, Researcher> researchers;
	private static String VALID = "valid";
	private static String ERROR = "error";
	private static Logger logger = Logger.getLogger("edu.hawaii.myisern.Orange");
	/**
	 * Constructor.
	 * @param collaborations Collaborations.
	 * @param organizations Organizations.
	 * @param researchers Researchers.
	 * @throws Exception if error occurs.
	 */
	public SanityCheck(ConcurrentMap<String, Collaboration> collaborations, 
	                   ConcurrentMap<String, Organization> organizations,
	                   ConcurrentMap<String, Researcher> researchers) throws Exception {
	  this.collaborations = collaborations;
		this.organizations = organizations;
		this.researchers = researchers;
		this.report = new ArrayList<SanityCheckErrorItem>();
		//verifyResearchers();
	}
	/**
   * Verifies all collaboration.
   * @throws Exception if error occurs.
   */
  public void verifyCollaborations() throws Exception {
    for (Collaboration collab : this.collaborations.values()) {
      this.report.add(verifyCollaboration(collab));
    }
  }
  /**
   * Checks the validation of a supplied collaboration object.
   * 
   * @param collab Collaboration to validate.
   * @return True if valid, False if not valid.
   * @throws Exception Handle thrown exceptions.
   */

  public SanityCheckErrorItem verifyCollaboration(Collaboration collab) throws Exception {
    SanityCheckErrorMessage errorMsg;
    SanityCheckErrorItem errorItem;
    try {
      List<String> collabOrgList = collab.getCollaboratingOrganizations()
          .getCollaboratingOrganization();
      List<String> collabTypeList = collab.getCollaborationTypes().getCollaborationType();
      List<String> outcomeTypeList = collab.getOutcomeTypes().getOutcomeType();
      //System.out.println(collab.getName());
      // valid name
        // valid string
      errorMsg = isValidString(collab.getName());
      errorItem = new SanityCheckErrorItem(collab, collab.getName());
      if (!errorMsg.isValid()) {
        errorItem.addError("Name: " + 
                           errorMsg.getMessage() + ".");
      }
      // valid Collaborating-Organizations
        // valid string
        // minimum amount of two
      // valid Collaboration-Type
        // valid string
        // minimum amount of one
      // valid Collaboration-Type
        // valid string
        // minimum amount of one
      // valid Year
        // between VALID_MIN_YEAR and VALID_MAX_YEAR
      // valid Outcome-Type
        // valid string
        // minimum amount of one
      // valid Description
        // valid string
      errorMsg = isValidString(collab.getDescription());
      //errorItem = new SanityCheckErrorItem(collab, collab.getDescription());
      if (!errorMsg.isValid()) {
        errorItem.addError("Description: " + 
                           errorMsg.getMessage() + ".");
      }
      return errorItem;
    }
    catch (Exception e) {
      logger.log(Level.SEVERE, e.toString());
      return null;
    }
  }
  
  /**
   * Verifies all organization.
   * @throws Exception if error occurs.
   */
  public void verifyOrganizations() throws Exception {
    for (Organization org : this.organizations.values()) {
      this.report.add(verifyOrganization(org));
    }
  }
  /**
   * Validates an organization.
   * 
   * @param org Organization to validate.
   * @return True if organization is valid, False if not.
   * @throws Exception Handle thrown exceptions.
   */

  private SanityCheckErrorItem verifyOrganization(Organization org) throws Exception {
    SanityCheckErrorMessage errorMsg;
    SanityCheckErrorItem errorItem;
    try {
      List<String> affilateResearcherList = org.getAffiliatedResearchers()
          .getAffiliatedResearcher();
      List<String> researchKeywordList = org.getResearchKeywords().getResearchKeyword();
      //System.out.println(org.getName());
      // valid name
        // valid string
      errorMsg = isValidString(org.getName());
      errorItem = new SanityCheckErrorItem(org, org.getName());
      if (!errorMsg.isValid()) {
        errorItem.addError("Name: " + 
                           errorMsg.getMessage() + ".");
      }
      // valid type
        // valid string
      errorMsg = isValidString(org.getType());
      //errorItem = new SanityCheckErrorItem(org, org.getType());
      if (!errorMsg.isValid()) {
        errorItem.addError("Type: " + 
                           errorMsg.getMessage() + ".");
      }
      // valid contact
        // valid string
      errorMsg = isValidString(org.getType());
      //errorItem = new SanityCheckErrorItem(org, org.getContact());
      if (!errorMsg.isValid()) {
        errorItem.addError("Contact: " + 
                           errorMsg.getMessage() + ".");
      }
      // valid affiliated-researcher
        // valid string
      // valid country
        // valid string
      errorMsg = isValidString(org.getCountry());
      //errorItem = new SanityCheckErrorItem(org, org.getCountry());
      if (!errorMsg.isValid()) {
        errorItem.addError("Country: " + 
                           errorMsg.getMessage() + ".");
      }
      // valid research-keyword
        // valid string
        // minimum amount of one
      // valid research-description
        // valid string
      errorMsg = isValidString(org.getResearchDescription());
      //errorItem = new SanityCheckErrorItem(org, org.getResearchDescription());
      if (!errorMsg.isValid()) {
        errorItem.addError("Description: " + 
                           errorMsg.getMessage() + ".");
      }
      // valid home-page
        // valid web link
      // isValidWebLink(org.getHomePage())
      return errorItem;
    }
    catch (Exception e) {
      logger.log(Level.SEVERE, e.toString());
      return null;
    }
  }
  
 /**
  * Returns the validation report;
  * @return The Report.
  */
public List<SanityCheckErrorItem> getReport() {
  return this.report;
}
/**
 * Verifies all researchers.
 * @throws Exception if error occurs.
 */
public void verifyResearchers() throws Exception {
  for (Researcher researcher : this.researchers.values()) {
    this.report.add(verifyResearcher(researcher));
  }
}
 /**
  * Validates a researcher.
  * 
  * @param researcher Researcher to validate.
  * @return True if researcher is valid, False if not valid.
  * @throws Exception Handle thrown exceptions.
  */
 public SanityCheckErrorItem verifyResearcher(Researcher researcher) throws Exception {
 SanityCheckErrorMessage errorMsg;
 SanityCheckErrorItem errorItem;
   try {
     // valid name
       // valid string
     errorMsg = isValidString(researcher.getName());
     errorItem = new SanityCheckErrorItem(researcher, researcher.getName());
     if (!errorMsg.isValid()) {
       errorItem.addError("Name: " + 
                          errorMsg.getMessage() + ".");
     }
     // valid organization
      // valid string
     /*
     errorItem.addError(researcher.getOrganization()  + ": " + 
                        "Organization un-verified.");
     */
     // valid email
       // email formated string
     errorMsg = isValidEmail(researcher.getEmail());
     if (!errorMsg.isValid()) {
       errorItem.addError("Email: " + 
                          errorMsg.getMessage() + ".");
     }
     // valid picture-link
       // valid web link
     //errorItem.addError("Picture-Link un-verified.");
     // valid bio-statement
       // valid string
     errorMsg = isValidString(researcher.getBioStatement());
     if (!errorMsg.isValid()) {
       errorItem.addError("Bio-Statement: " + 
                          errorMsg.getMessage() + ".");
     }
     return errorItem;
   }
   catch (Exception e) {
     logger.log(Level.SEVERE, e.toString());
     return null;
   }
 }
  /**
   * Validates a string.
   * 
   * @param s String to validate.
   * @return True if string is valid, False if not valid.
   */
  private static SanityCheckErrorMessage isValidString(String s) {
  String msg = "";
  boolean pass = true;
    try {
      // not ""
      if ("".equals(s)) {
      msg = "blank";
        pass = false;
      }

      // not null
      else if (s == null) {
      msg = "null";
        pass = false;
      }
      else {
      msg = VALID;
        pass = true;
      }
      return new SanityCheckErrorMessage(msg,pass);
    }
    catch (Exception e) {
      logger.log(Level.SEVERE, e.toString());
      return null;
    }
  }

  /**
   * Validates the year according to the VALID_MIN_YEAR & VALID_MAX_YEAR values.
   * 
   * @param year Year to validate.
   * @return True if year is valid, False if not valid.
   * @throws Exception Handle thrown exceptions.
   */
  public static SanityCheckErrorMessage isValidYear(BigInteger year) throws Exception {
  String msg = "";
    boolean pass = true;
    try {
      int curYear = year.intValue();
      // then check most recent year is between min year && max year
      if (1990 <= curYear && curYear <= 2010) {
        // year is valid and we can attempt to add the unique id to the list
      msg = VALID;
        pass = true;
      }
      else {
      msg = "year not between 1990 && 2010";
        pass = false;
      }
      return new SanityCheckErrorMessage(msg, pass);
    }
    catch (Exception e) {
      logger.log(Level.SEVERE, e.toString());
      return null;
  }
  }
  /**
   * Validates an email address.
   * 
   * @param email Email address to validate.
   * @return True if email is valid, False if not valid.
   * @throws Exception Handle thrown exceptions.
   */
  private SanityCheckErrorMessage isValidEmail(String email) throws Exception {
    /*
     * Code borrowed and inspired by Sun Corporation
     * http://java.sun.com/developer/technicalArticles/releases/1.4regex/index.html
     */
  String msg = "";
    boolean pass = true;
    try {
      // Checks for empty address
      if (email == null || email.isEmpty()) {
        msg = "empty/null";
        pass = false;
      }
      else {
        // Checks for email addresses starting with
        // inappropriate symbols like dots or @ signs.
        Pattern p = Pattern.compile("^\\.|^\\@");
        Matcher m = p.matcher(email);
        if (m.find()) {
          msg = "starts with an inappropriate symbols like dots or @ signs";
          pass = false;
        } 
        else {
          // Checks for email addresses that start with
          // www. and prints a message if it does.
          p = Pattern.compile("^www\\.");
          m = p.matcher(email);
          if (m.find()) {
            msg = "starts with www";
            pass = false;
          }
          else {
            // Check for email to be in [name]@[domain] format
            // '\\w' == [a-zA-Z0-9]
            p = Pattern.compile("^[\\w|\\.]+@[\\w|\\.]+");
            m = p.matcher(email);
            if (m.find()) {
              msg = VALID;
              pass = true;
            }
            else {
              msg = "not in [name]@[domain] format";
              pass = false;
            }
          }
        }
      }
      return new SanityCheckErrorMessage(msg, pass);
    }
    catch (Exception e) {
      logger.log(Level.SEVERE, e.toString());
      return null;
    }
  }

  /**
   * Checks validation of web links..
   * 
   * @param url URL to test.
   * @throws SAXException Thrown exception.
   * @throws IOException Thrown exception.
   * @throws MalformedURLException Thrown exception.
   * @return if valid or not.
   */
  public SanityCheckErrorMessage isValidWebLink(String url) throws MalformedURLException, 
                                                   IOException, 
                                                   SAXException {
    try {
      WebConversation wc = new WebConversation();
      WebResponse response = wc.getResponse(url);
      return new SanityCheckErrorMessage(VALID, true);
    }
    catch (UnknownHostException e) {
      SanityCheckErrorMessage scem = new SanityCheckErrorMessage(ERROR, false);
      scem.setErrorMessage(e.getMessage());
      return scem;
    }
    catch (Exception e) {
      SanityCheckErrorMessage scem = new SanityCheckErrorMessage(ERROR, false);
      scem.setErrorMessage(e.getMessage());
      return scem;
    }

  }
}